package com.example.demo.util;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.*;

public class FileUtil extends FileCopyUtils {

    private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);

    /**
     * 删除提供的 File 对于目录（递归删除所有嵌套的目录或文件）。如果目录不存在也会返回 false
     */
    public static boolean deleteRecursively(@Nullable File root) {
        return FileSystemUtils.deleteRecursively(root);
    }

    /**
     * 删除提供的 File 对于目录（递归删除所有嵌套的目录或文件）。如果目录不存在也会返回 false
     */
    public static boolean deleteRecursively(@Nullable Path root) throws IOException {
        return FileSystemUtils.deleteRecursively(root);
    }

    /**
     * 将src的目录递归的方式拷贝到dest目录下
     */
    public static void copyRecursively(File src, File dest) throws IOException {
        FileSystemUtils.copyRecursively(src, dest);
    }


    /**
     * 将src的目录递归的方式拷贝到dest目录下
     */
    public static void copyRecursively(Path src, Path dest) throws IOException {
        FileSystemUtils.copyRecursively(src, dest);
    }

    /**
     * 获取文件后缀
     *
     * @return txt/pdf/html/doc...
     */
    public static String getFileSuffix(String filePath) {
        if (filePath == null || filePath.length() == 0) {
            return "";
        }
        int indexOf = filePath.lastIndexOf(".");
        return filePath.substring(indexOf + 1);
    }


    public static void saveFile(InputStream inputStream, String filePath) throws IOException {
        saveFile(inputStream, new File(filePath));
    }

    public static void saveFile(InputStream inputStream, File file) throws IOException {
        if (!file.exists()) {
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            file.createNewFile();
        }
        // 返回值为该输入流的字节数
        FileCopyUtils.copy(inputStream, new FileOutputStream(file));
    }


    public static void append(String txtPath, String appendString) throws IOException {
        File file = new File(txtPath);
        append(file, appendString);
    }

    public static void append(File txtPath, String appendString) throws IOException {
        if (!txtPath.exists()) {
            File parentFile = txtPath.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            txtPath.createNewFile();
        }
        Writer fileWriter = new FileWriter(txtPath, true);
        fileWriter.append(appendString);
        fileWriter.flush();
        close(fileWriter);
    }
//=====================================read/write读取文件=======================================================================

    public static List<String> readLines(String filePath) throws IOException {
        return readLines(new File(filePath));
    }

    public static List<String> readLines(File file) throws IOException {
        if (!file.exists()) {
            throw new FileNotFoundException("the path is not found");
        }
        if (file.isDirectory()) {
            throw new IOException("the path is not file");
        }
        BufferedReader br = null;
        try {
            List<String> txtLines = new ArrayList<>();
            br = new BufferedReader(new FileReader(file));
            String lineStr;
            while ((lineStr = br.readLine()) != null) {
                txtLines.add(lineStr);
            }
            return txtLines;
        } finally {
            close(br);
        }
    }

    public static String readAll(File readFile) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(readFile);
        byte[] fileStrBytes = StreamUtil.copyToByteArray(fileInputStream);
        close(fileInputStream);
        return new String(fileStrBytes);
    }

    public static void write(File file, String txt) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(file);
        if (txt == null) {
            StreamUtil.write(new byte[0], 0, 0, outputStream);
        }
        byte[] bytes = txt.getBytes(StandardCharsets.UTF_8);
        StreamUtil.write(bytes, 0, bytes.length, outputStream);
        close(outputStream);
    }
//===================================================文件的切割与合并==========================================================

    public static void main(String[] args) throws IOException {
        String mainFilePath = "D:\\Tools\\IdeaIU\\ideaIU-2019.2.3.win.zip";
        long stepFileLen = 1024 * 1024 * 4;// 4MB
        getStepFileMd5(mainFilePath, stepFileLen);
    }

    //获取分段文件的MD5
    public static List<String> getStepFileMd5(String mainFilePath, long stepFileLen) throws IOException {
        File file = new File(mainFilePath);
        long fileLen = file.length();
        // 按照多少长度为一段
        List<String> stepMd5List = new ArrayList<>((int) ((fileLen / stepFileLen) + 1));
        FileInputStream fileInputStream = new FileInputStream(file);
        for (long startLen = 0; startLen < fileLen; startLen += stepFileLen) {
            // 前闭后开原则
            int len = (int) (Math.min(startLen + stepFileLen, fileLen) - startLen);
            byte[] bytes = new byte[len];
            if (len != fileInputStream.read(bytes, 0, len)) {
                System.err.println("读取长度异常。");
            }
            stepMd5List.add(DigestUtil.md5DigestAsHex(bytes));
        }
        fileInputStream.close();
        return stepMd5List;
    }



    public static List<File> findGT4GB(String baseDir) {
        File file = new File(baseDir);
        List<File> all4GBFile = new ArrayList<>();
        doFindGT4GB(file, all4GBFile);
        return all4GBFile;
    }

    private static void doFindGT4GB(File file, List<File> all4GBFile) {
        if (file.isFile()) {
            long _4GB = 4294967296L;// (1 << 30) * 4L;
            if (file.length() > _4GB) {
                all4GBFile.add(file);
            }
        } else {
            File[] files = file.listFiles();
            if (files != null && files.length > 0) {
                for (File file1 : files) {
                    doFindGT4GB(file1, all4GBFile);
                }
            }
        }
    }

    /**
     * 对 {@link #splitFile(String)} 方法拆分后的文件进行合并，参数的顺序必须一致
     *
     * @param allFilePath
     * @param outFile
     * @throws IOException
     */
    public static void mergeFile(ArrayList<String> allFilePath, String outFile) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(outFile);
        byte[] bytes = new byte[StreamUtil.BUFFER_SIZE];
        int len;
        for (int i = 0; i < allFilePath.size(); i++) {
            String filePath = allFilePath.get(i);
            FileInputStream fileInputStream = new FileInputStream(filePath);
            while ((len = fileInputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
            close(fileInputStream);
        }
        flush(outputStream);
        close(outputStream);
    }

    public static ArrayList<String> splitFile(String filePath) throws IOException {
        /*
         * 1 << 10   1KB
         * 1 << 20   1MB
         * 1 << 30   1GB
         */
        int cardinalNumber = 1 << 30;
        return splitFile(filePath, cardinalNumber);
    }

    /**
     * 对文件按照1GB的规则进行切割，比如讲一个4GB的文件切割后会变成四个1GB文件
     *
     * @param filePath
     * @param cardinalNumber 分割长度
     */
    public static ArrayList<String> splitFile(String filePath, int cardinalNumber) throws IOException {
        ArrayList<String> outPath = new ArrayList<>();
        File file = new File(filePath);
        if (!file.exists() || !file.isFile() || file.length() < cardinalNumber) {
            // 如果目录不存在或者该目录不是一个文件，就不做任何处理
            return outPath;
        }
        long readLen = 0;
        FileInputStream fileInputStream = new FileInputStream(file);
        byte[] bytes = new byte[StreamUtil.BUFFER_SIZE];
        int len;
        int currentDocNum = 0;
        String op = filePath + "." + currentDocNum;
        OutputStream outputStream = new FileOutputStream(op);
        outPath.add(op);
        while ((len = fileInputStream.read(bytes)) != -1) {
            readLen += len;
            if (readLen > cardinalNumber) {
                currentDocNum++;
                readLen = 0;
                flush(outputStream);
                close(outputStream);
                op = filePath + "." + currentDocNum;
                outPath.add(op);
                outputStream = new FileOutputStream(op);
            }
            outputStream.write(bytes, 0, len);
        }
        flush(outputStream);
        close(outputStream);
        return outPath;
    }

//===================================================ZIP================================================================

    /**
     * 解压zip文件
     *
     * @param src  zip压缩包的路径
     * @param desc 解压的目录
     */
    public static void unZip(String src, String desc) throws IOException {
        File descFile = new File(desc);
        if (!descFile.exists()) {
            descFile.mkdirs();
        }
        File srcFile = new File(src);
        ZipFile zipFile = new ZipFile(srcFile);
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            File tempFile = new File(desc + File.separator + entry.getName());
            if (entry.isDirectory()) {
                tempFile.mkdirs();
            } else {
                tempFile.createNewFile();
                InputStream in = zipFile.getInputStream(entry);
                copy(in, new FileOutputStream(tempFile));
            }
        }
        close(zipFile);
    }

    /**
     * @param srcFilePath  文件夹或文件
     * @param destFilePath 压缩后的目标文件
     * @throws IOException
     */
    public static void zip(String srcFilePath, String destFilePath, Collection<String> filterNames) throws IOException {
        zip(new File(srcFilePath), new File(destFilePath), filterNames);
    }

    public static void zip(File src, File zipFile) throws IOException {
        zip(src, zipFile, null);
    }

    public static void zip(File src, File zipFile, Collection<String> filterNames) throws IOException {
        if (!src.exists()) {
            throw new FileNotFoundException("源文件目录不存在：" + src);
        }
        if (zipFile.exists()) {
            throw new FileAlreadyExistsException("目标文件已存在：" + zipFile);
        }
        FileOutputStream fos = new FileOutputStream(zipFile);
        CheckedOutputStream cos = new CheckedOutputStream(fos, new CRC32());
        ZipOutputStream zos = new ZipOutputStream(cos);
        doCompress(src, zos, "", filterNames);
        zos.close();
    }

    private static void doCompress(File src, ZipOutputStream zos, String baseDir, Collection<String> filterNames) throws IOException {
        if (!src.exists())
            return;
        if (filterNames != null && filterNames.contains(src.getName()) && src.isDirectory()) {
            return;
        }
        if (src.isFile()) {
            logger.debug("正在压缩的文件：" + src);
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
            ZipEntry entry = new ZipEntry(baseDir + src.getName());
            zos.putNextEntry(entry);
            StreamUtil.copy(bis, zos);
            close(bis);
        } else if (src.isDirectory()) {
            File[] files = src.listFiles();
            if (files == null) {
                return;
            }
            if (files.length == 0) {
                zos.putNextEntry(new ZipEntry(baseDir + src.getName() + File.separator));
            } else {
                for (File file : files) {
                    doCompress(file, zos, baseDir + src.getName() + File.separator, filterNames);
                }
            }
        }
    }

    public static void flush(OutputStream outputStream) {
        try {
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void close(Closeable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (IOException e) {

        }
    }
}
